---
layout: ../../layouts/PageLayout.astro
title: Subscriptions
description: Learn how to run GraphQL subscriptions
order: 5
---

import DocTip from '@/components/DocTip.vue';

# Subscriptions

`villus` handles subscriptions with the `useSubscription` function.

To add support for subscriptions you need to add the `handleSubscriptions` plugin to the `useClient` plugin list, which in turn will call your subscription client. The plugin expects a function that returns an object that follows the [observable spec](https://github.com/tc39/proposal-observable) to be returned, this function is called a **subscription forwarder**.

You can use [`graphql-ws`](https://github.com/enisdenjo/graphql-ws) package for your subscriptions implemented with websockets protocol, so one way to build your subscription forwarder is like this:

```vue
<script setup>
import { useClient } from 'villus';
import { createClient } from 'graphql-ws';

const wsClient = createClient({
  url: 'ws://localhost:9005/graphql',
});

const subscriptionsHandler = handleSubscriptions(operation => {
  return {
    subscribe: obs => {
      wsClient.subscribe(
        {
          query: operation.query,
          variables: operation.variables,
        },
        obs,
      );

      return {
        unsubscribe: () => {
          // No OP
        },
      };
    },
  };
});

const client = useClient({
  url: 'http://localhost:4000/graphql',
  // Install the subscriptions handler
  use: [subscriptionsHandler, ...defaultPlugins()],
});
</script>
```

## Executing Subscriptions

The `useSubscription` function has a similar API as it exposes a `data` property that you can watch

```vue
<template>
  <ul>
    <li v-for="message in messages">{{ message }}</li>
  </ul>
</template>

<script setup>
import { watch, ref } from 'vue';
import { useSubscription } from 'villus';

const NewMessages = `
  subscription NewMessages {
    newMessages {
      id
      from
      message
    }
  }
`;

const messages = ref([]);
useSubscription({ query: NewMessages }, ({ data, error }) => {
  // do stuff with incoming data
  if (data) {
    messages.value.push(...data.newMessages);
  }

  // Handler errors
  if (error) {
  }
});
</script>
```

## Passing Variables

You can pass variables to subscriptions by passing an object containing both `query` and `variables` as the first argument:

```vue
<script setup>
import { useSubscription } from 'villus';

const NewMessages = `
  subscription ConversationMessages ($id: ID!) {
    conversation(id: $id) {
      id
      from
      message
    }
  }
`;

const { data } = useSubscription({
  query: NewMessages,
  variables: { id: 1 },
});
</script>
```

## Handling Subscription Data

The previous examples are not very useful, as usually you would like to be able to use the `data` as a continuous value rather than a reference to the last received value, that is why you can pass a custom mapper or a reducer as the second argument to the `useSubscription` function. This callback function receives the new result from the subscription as the first argument, and the reduced data if available as the second argument. You can use it as either a mapper or a reducer depending on what you return from the function.

to understand the difference between a mapper and a reducer check the next couple of examples.

### Mapping data

In the following example, the passed function only extracts the `lastMessage` field out from the response data. That means it only returns a mapped version of the last result recieved.

```vue {19-22}
<script setup>
import { useSubscription } from 'villus';

const LastMessage = `
  subscription LastMessage {
    lastMessage {
      id
      from
      message
    }
  }
`;

// rename data to be more descriptive of its usage
const { data: lastMessage } = useSubscription(
  {
    query: LastMessage,
  },
  ({ data }) => {
    // remember that data can be null
    return data?.lastMessage;
  },
);

// anywhere
lastMessage.value; // { id: 1, from: 'someone', message: 'hello' }
</script>
```

This is useful for subscriptions that represent a live state for something on your API. You are not limited to mapping the results in a specific way, for example if you want to return a boolean, you can do so:

```vue {17-19}
<script setup>
import { useSubscription } from 'villus';

const UnreadMessages = `
  subscription UnreadMessages {
    unreadMessages {
      id
    }
  }
`;

// rename data to be more descriptive of its usage
const { data: hasUnread } = useSubscription(
  {
    query: UnreadMessages,
  },
  ({ data }) => {
    return data?.unreadMessages.length > 1;
  },
);

// anywhere
hasUnread.value; // true or false
</script>
```

So it is completely is up to you how to map the data and make them useful to your needs.

### Reducing data

The reducer is a subscription handler that aggregates past and future results into a single value. The aggregated value will become the `data` returned from `useSubscription`.

Here is the last example with a custom reducer:

```vue {19-29}
<script setup>
import { useSubscription } from 'villus';

const NewMessages = `
  subscription NewMessages {
    newMessages {
      id
      from
      message
    }
  }
`;

// rename data to make it more clear
const { data: messages } = useSubscription(
  {
    query: NewMessages,
  },
  ({ data, error }, oldValue) => {
    // old value is nullable
    oldValue = oldValue || [];
    // in case of error just return the last value
    if (!data || error) {
      return oldValue;
    }

    // combine old and incoming messages
    return [...oldValue, response.data.newMessages];
  },
);

messages.value; // { id, from, message }[]
</script>
```

The function here acts as a reducer for the incoming data, whenever a new response is received it will be passed to `reduceMessages` function as the second argument, the first argument will be the previous value. This makes it more compact than having to declare state or watching over the data yourself.

<DocTip>

Keep in mind that initially, we have `null` for the initial value so we needed to provide a fallback for that.

</DocTip>

## Pausing subscriptions

Similar to queries, subscriptions could also be paused by passing a `paused` value to `useSubscription`.

```vue
<script setup>
import { ref } from 'vue';
import { useSubscription } from 'villus';

const NewMessages = `
  subscription ConversationMessages ($id: ID!) {
    conversation(id: $id) {
      id
      from
      message
    }
  }
`;

const paused = ref(false);

const { data } = useSubscription({
  query: NewMessages,
  variables: { id: 1 },
  paused,
});

// pause the subscription
paused.value = true;

// resume the subscription
paused.value = false;
</script>
```

Note that pausing or unpausing doesn't sever the established connection (if websocket implementation is used), all it does is ignore the incoming values.
